package lp.polimi.treefinder;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

import lp.polimi.treefinder.LocalizationFragment.LocalizationFragmentListener;
import lp.polimi.treefinder.MyTagsFragment.MyTagsFragmentListener;
import lp.polimi.treefinder.RankingDialogFragment.RankingDialogListener;
import lp.polimi.treefinder.TaggingFragment.TaggingFragmentListener;
import lp.polimi.treefinder.client.Connection;
import lp.polimi.treefinder.client.DefaultTCPConnection;
import lp.polimi.treefinder.utils.FilesUtils;
import lp.polimi.treefinder.utils.FilesUtils.SaveMode;
import lp.polimi.treefinder.utils.ManagingMessage;
import lp.polimi.treefinder.utils.ManagingMessage.Type;
import lp.polimi.treefinder.utils.RankElement;
import lp.polimi.treefinder.utils.UIUtils;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DialogFragment;
import android.app.FragmentTransaction;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

/**
 * The app's main activity, allowing the user to tag a leaf, show the list
 * of tagged leaves and access the settings activity.
 */
public class MainActivity extends MediaMountedActivity implements RankingDialogListener, LocalizationFragmentListener,
TaggingFragmentListener, MyTagsFragmentListener {

	private static final String CURRENTLY_SELECTED_TAB = "selTab";

	private static final int MATCHING_STEP = 3;
	private static final int SEND_REQUEST_STEP = 2;
	private static final int CONNECTION_STEP = 1;

	public static final String HOST = "192.168.43.84"; //"treefinder.bounceme.net";
	public static final int SERVER_PORT = 4445;

	public static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
	public static final int IMAGE_SELECT_CODE = 0;

	private static final int VIBRATION_TIME = 300;

	private AsyncTask<String, Integer, ArrayList<RankElement>> taggingTask;
	private SharedPreferences sharedPref;
	private Vibrator vibrator = null;
	private ArrayList<RankElement> ranking;
	private ProgressDialog locPd;
	private Intent leafDataIntent;

	private LeafState leafState = LeafState.KNOWN;
	private String unknownLeafId = null;


	@Override
	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_main);

		PreferenceManager.setDefaultValues(this, R.xml.main_settings, false);
		sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
		if (getSharedPreferences("firstRun", MODE_PRIVATE).getBoolean("firstRun", true)) {
			UIUtils.showWelcomeDialog(this);
		}

		vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);

		if (savedInstanceState!=null)
			ranking = savedInstanceState.getParcelableArrayList("ranking");

		/*Setup action bar tabs: a tab for the Tagging Fragment (take photo or load from file)
		 * and the other for the MyTagFragment (list of tagged leaves).
		 * */
		ActionBar actionBar = getActionBar();

		actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

		Tab tab = actionBar.newTab()
				.setText(R.string.tagging)
				.setTabListener(new TabListener<TaggingFragment>(
						this, "tagging", TaggingFragment.class));
		actionBar.addTab(tab, 0);

		tab = actionBar.newTab()
				.setText(R.string.myTags)
				.setTabListener(new MyTagsTabListener(this, "myTags", "myTagsEmpty"));
		actionBar.addTab(tab, 1);


	}



	@Override
	protected void onResume() {

		super.onResume();

		if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("location", false)
				&& getFragmentManager().findFragmentByTag("localization")==null) {
			FragmentTransaction ft = getFragmentManager().beginTransaction();
			ft.add(new LocalizationFragment(), "localization");
			ft.commit();
			Log.d("Localization", "Added localization fragment.");
		}

	}

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);

		if (savedInstanceState!=null)
			getActionBar().setSelectedNavigationItem(savedInstanceState.getInt(CURRENTLY_SELECTED_TAB));
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putInt(CURRENTLY_SELECTED_TAB, getActionBar().getSelectedNavigationIndex());
		outState.putParcelableArrayList("ranking", ranking);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		//open the SettingsActivity
		case R.id.settings:
			Intent i = new Intent(this, SettingsActivity.class);
			startActivity(i);
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}


	@Override
	/**
	 * Load image file and start the tagging task.
	 */
	public void processImage (int requestCode, int resultCode, Intent data) {
		switch (requestCode) {
		//if the image comes from the camera app
		case CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE:
			if (resultCode == Activity.RESULT_OK) {

				File leaf = new File (this.getExternalFilesDir(Environment.DIRECTORY_PICTURES), "tmp.jpg");

				taggingTask = new TaggingTask();
				taggingTask.execute(leaf.getPath());


			} else if (resultCode == Activity.RESULT_CANCELED) {
				UIUtils.unlockScreenRotation(this);
			} else {
				Toast.makeText(this, R.string.camera_unavailable, Toast.LENGTH_LONG).show();
				UIUtils.unlockScreenRotation(this);
			}
			break;
			// if the image has been loaded from file
		case IMAGE_SELECT_CODE:
			if (resultCode == Activity.RESULT_OK && data!=null) {
				Uri selectedImage = data.getData();
				String[] filePathColumn = { MediaStore.Images.Media.DATA };

				Cursor cursor = this.getContentResolver().query(selectedImage, filePathColumn, null, null, null);
				cursor.moveToFirst();

				int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
				String picturePath = cursor.getString(columnIndex);
				cursor.close();

				if (picturePath.startsWith("/storage/")) {
					taggingTask = new TaggingTask();
					taggingTask.execute(picturePath);
				} else {
					new AlertDialog.Builder(this).setTitle(R.string.not_valid_image).
					setMessage(R.string.not_valid_image_message).
					setPositiveButton(R.string.ok, null).create().show();
				}
			}
			break;
		default:
			super.onActivityResult(requestCode, resultCode, data);
		}	
	}

	/**
	 * Save an image of the tagged species, update the tagged leaves XML file
	 * and start the LeafInsightsActivity to display details about the just tagged leaf.
	 * See the LeafState enum for the meaning of KNOWN, UNKNOWN, ALREADY_UNKNOWN states.
	 */
	private void saveLeafAndDisplayResult(String name, byte[] image) {

		Date date = new Date();

		try {

			if (leafState.equals(LeafState.UNKNOWN)) {
				unknownLeafId = Long.toString(date.getTime());
				FilesUtils.saveUnknownLeaf(this, image, unknownLeafId);
			} else {
				FilesUtils.saveLeafBytes(this, image, name, SaveMode.PNG);
				FilesUtils.saveLeafBytes(this, image, name, SaveMode.JPEG_SQUARED);
			}


			if (leafState.equals(LeafState.PREVIOUSLY_UNKNOWN)) {
				XMLHandler.updateXMLFile(this, unknownLeafId, name);
				new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "unknownLeaf_"+unknownLeafId+".png").delete();

				getActionBar().setSelectedNavigationItem(1);
				UIUtils.unlockScreenRotation(MainActivity.this);
				return;
			}

			leafDataIntent = new Intent(this, LeafInsightsActivity.class);
			leafDataIntent.putExtra(XMLHandler.KEY_NAME, name);

			java.text.DateFormat dateFormat = DateFormat.getDateFormat(this);
			leafDataIntent.putExtra(XMLHandler.KEY_DATE, dateFormat.format(date));



			if (sharedPref.getBoolean("location", false)) {

				locPd = ProgressDialog.show(this, getResources().getString(R.string.retrieving_location),
						getResources().getString(R.string.retrieving_location_message), true);
				//getlocation
				LocalizationFragment locFrag = (LocalizationFragment) this.getFragmentManager().findFragmentByTag("localization");
				locFrag.getCurrentLocation();

			} else {

				if (leafState.equals(LeafState.UNKNOWN)) {
					XMLHandler.writeXMLFile(this, leafDataIntent.getExtras().getString(XMLHandler.KEY_NAME),
							leafDataIntent.getExtras().getString(XMLHandler.KEY_DATE), null, null, null, unknownLeafId);
					getActionBar().setSelectedNavigationItem(1);
				} else {
					leafDataIntent.putExtra(XMLHandler.KEY_ADDRESS, getResources().getString(R.string.not_address));
					leafDataIntent.putExtra(XMLHandler.KEY_CITY, getResources().getString(R.string.not_city));
					leafDataIntent.putExtra(XMLHandler.KEY_COUNTRY, getResources().getString(R.string.not_country));

					XMLHandler.writeXMLFile(this, leafDataIntent.getExtras().getString(XMLHandler.KEY_NAME),
							leafDataIntent.getExtras().getString(XMLHandler.KEY_DATE), null, null, null, null);

					startActivity(leafDataIntent);
				}
				UIUtils.unlockScreenRotation(this);
			}

		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	@Override
	public void onRankingDialogPositiveClick(int choice) {
		saveLeafAndDisplayResult(ranking.get(choice).getName(), ranking.get(choice).getPhoto());
	}

	@Override
	public void onRankingDialogNegativeClick() {
		UIUtils.unlockScreenRotation(this);
	}

	@Override
	/**
	 * Called by the Localization Fragment after it has retrieved the address related to the current
	 * location of the device. All the details about the tagged leaf (name, address and date) are stored in the XML File.
	 */
	public void onAddressRetrieved(String[] result) {
		if (result!=null && !(result[0]==null && result[1]==null && result[2]==null)) {
			if (result[0]!=null)
				leafDataIntent.putExtra(XMLHandler.KEY_ADDRESS, result[0]);
			else
				leafDataIntent.putExtra(XMLHandler.KEY_ADDRESS, getResources().getString(R.string.not_address));
			if (result[1]!=null)
				leafDataIntent.putExtra(XMLHandler.KEY_CITY, result[1]);
			else
				leafDataIntent.putExtra(XMLHandler.KEY_CITY, getResources().getString(R.string.not_city));
			if (result[2]!=null)
				leafDataIntent.putExtra(XMLHandler.KEY_COUNTRY, result[2]);
			else
				leafDataIntent.putExtra(XMLHandler.KEY_COUNTRY, getResources().getString(R.string.not_country));

			if (leafState.equals(LeafState.UNKNOWN)) {
				XMLHandler.writeXMLFile(this, leafDataIntent.getExtras().getString(XMLHandler.KEY_NAME),
						leafDataIntent.getExtras().getString(XMLHandler.KEY_DATE), result[0], result[1], result[2], unknownLeafId);
				getActionBar().setSelectedNavigationItem(1);
			} else {
				XMLHandler.writeXMLFile(this, leafDataIntent.getExtras().getString(XMLHandler.KEY_NAME),
						leafDataIntent.getExtras().getString(XMLHandler.KEY_DATE), result[0], result[1], result[2], null);
				startActivity(leafDataIntent);
			}
			locPd.dismiss();
			UIUtils.unlockScreenRotation(MainActivity.this);

		} else {

			locPd.dismiss();


			new AlertDialog.Builder(this).setTitle(R.string.location_error).setMessage(R.string.location_error_message).
			setPositiveButton(R.string.tag_anyway, new DialogInterface.OnClickListener() {

				@Override
				public void onClick(DialogInterface dialog, int which) {
					if (leafState.equals(LeafState.UNKNOWN)) {
						XMLHandler.writeXMLFile(MainActivity.this, leafDataIntent.getExtras().getString(XMLHandler.KEY_NAME),
								leafDataIntent.getExtras().getString(XMLHandler.KEY_DATE), null, null, null, unknownLeafId);
						getActionBar().setSelectedNavigationItem(1);
					} else {
						XMLHandler.writeXMLFile(MainActivity.this, leafDataIntent.getExtras().getString(XMLHandler.KEY_NAME),
								leafDataIntent.getExtras().getString(XMLHandler.KEY_DATE), null, null, null, null);
						leafDataIntent.putExtra(XMLHandler.KEY_ADDRESS, getResources().getString(R.string.not_address));
						leafDataIntent.putExtra(XMLHandler.KEY_CITY, getResources().getString(R.string.not_city));
						leafDataIntent.putExtra(XMLHandler.KEY_COUNTRY, getResources().getString(R.string.not_country));
						startActivity(leafDataIntent);
					}
					UIUtils.unlockScreenRotation(MainActivity.this);
				}
			})
			.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {

				@Override
				public void onClick(DialogInterface dialog, int which) {
					UIUtils.unlockScreenRotation(MainActivity.this);
				}
			}).create().show();
		}
	}



	@Override
	/** Starts the tagging procedure for an untagged leaf previously stored by the user.
	 * This method is called when the user select an entry in the tagged leaves list corresponding
	 * to an untagged leaf.
	 */
	public void tagUnknownLeaf(String unknownId) {
		this.unknownLeafId=unknownId;
		leafState=LeafState.PREVIOUSLY_UNKNOWN;
		taggingTask = new TaggingAlreadyUnknownLeafTask();
		taggingTask.execute(unknownId);

	}





	/**
	 * Performs the tagging procedure for previously stored untagged leaves: it basically sends a request
	 * containing the query leaf to the server, which processes it and returns the final ranking given the query.
	 */
	private class TaggingAlreadyUnknownLeafTask extends AsyncTask<String, Integer, ArrayList<RankElement>> {

		private ProgressDialog pd;
		private Connection conn = null;

		@Override
		protected void onPreExecute() {
			UIUtils.lockScreenRotation(MainActivity.this);
			pd = ProgressDialog.show(MainActivity.this, getResources().getString(R.string.tagging),
					getResources().getString(R.string.step_connection), true, false,
					new DialogInterface.OnCancelListener() {

				@Override
				public void onCancel(DialogInterface dialog) {
					taggingTask.cancel(true);
					onPostExecute(null);
					if (conn!=null)
						conn.closeSocket();
					UIUtils.unlockScreenRotation(MainActivity.this);
				}
			});
		}

		@Override
		protected void onPostExecute(ArrayList<RankElement> result) {
			pd.dismiss();

			// Vibration
			if (sharedPref.getBoolean("vibrate", false))
				vibrator.vibrate(VIBRATION_TIME);

			UIUtils.unlockScreenRotation(MainActivity.this);

			if (result==null) {
				new AlertDialog.Builder(MainActivity.this).setTitle(R.string.connection_error).setMessage(R.string.connection_error_alreadyKnownLeaf_message)
				.setPositiveButton(R.string.ok, null).create().show();


			} else {
				DialogFragment dialog = RankingDialogFragment.newInstance(result);
				dialog.show(getFragmentManager(), "ranking");
			}
		}



		@Override
		protected ArrayList<RankElement> doInBackground(String... id) {

			try {
				byte[] image = FilesUtils.retrieveUnknownLeaf(MainActivity.this, id[0]);
				conn = new DefaultTCPConnection(HOST, SERVER_PORT);
				return match(image, conn);

			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}


		private ArrayList<RankElement> match(byte[] image, Connection conn) throws Exception {

			publishProgress(CONNECTION_STEP);

			conn.initialize();
			ManagingMessage request = new ManagingMessage(Type.MATCHING_REQUEST);

			request.putProperty("image", image);

			publishProgress(SEND_REQUEST_STEP);
			conn.send(request);

			publishProgress(MATCHING_STEP);
			ManagingMessage reply = conn.receive();
			if (reply.getType()==Type.RANKING)
				ranking = (ArrayList<RankElement>)reply.getProperty("ranking");
			conn.closeSocket();


			return ranking;
		}


		@Override
		protected void onProgressUpdate(Integer... values) {
			switch (values[0]) {
			case SEND_REQUEST_STEP:
				pd.setCancelable(true);
				pd.setMessage(getResources().getString(R.string.step_send));
				break;
			case MATCHING_STEP:
				pd.setMessage(getResources().getString(R.string.step_matching));
				break;
			}
		}

	}




	/**
	 * Performs the tagging procedure for a leaf coming from the camera or loaded frm file: it basically sends a request
	 * containing the query leaf to the server, which processes it and returns the final ranking given the query.
	 */
	private class TaggingTask extends AsyncTask<String, Integer, ArrayList<RankElement>> {

		private ProgressDialog pd;
		private byte[] image;
		private Connection conn = null;

		@Override
		protected void onPreExecute() {
			UIUtils.lockScreenRotation(MainActivity.this);
			pd = ProgressDialog.show(MainActivity.this, getResources().getString(R.string.tagging),
					getResources().getString(R.string.step_connection), true, false,
					new DialogInterface.OnCancelListener() {
				@Override
				public void onCancel(DialogInterface dialog) {
					taggingTask.cancel(true);
					onPostExecute(null);
					if (conn!=null)
						conn.closeSocket();
				}
			});
		}


		@Override
		protected void onPostExecute(ArrayList<RankElement> result) {
			pd.dismiss();

			// Vibration
			if (sharedPref.getBoolean("vibrate", false))
				vibrator.vibrate(VIBRATION_TIME);

			if (result==null) {
				//error contacting the server
				leafState=LeafState.UNKNOWN;
				new AlertDialog.Builder(MainActivity.this).setTitle(R.string.connection_error).setMessage(R.string.connection_error_message)
				.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {

					@Override
					public void onClick(DialogInterface dialog, int which) {
						UIUtils.unlockScreenRotation(MainActivity.this);
					}
				})
				.setPositiveButton(R.string.store_features, new DialogInterface.OnClickListener() {

					@Override
					public void onClick(DialogInterface dialog, int which) {
						saveLeafAndDisplayResult(getResources().getString(R.string.unknown_leaf), image);
					}
				})
				.create().show();
			} else {
				leafState=LeafState.KNOWN;
				DialogFragment dialog = RankingDialogFragment.newInstance(result);
				UIUtils.unlockScreenRotation(MainActivity.this);
				dialog.show(getFragmentManager(), "ranking");
			}
		}

		@Override
		protected void onProgressUpdate(Integer... values) {
			switch (values[0]) {
			case SEND_REQUEST_STEP:
				pd.setCancelable(true);
				pd.setMessage(getResources().getString(R.string.step_send));
				break;
			case MATCHING_STEP:
				pd.setMessage(getResources().getString(R.string.step_matching));
				break;
			}
		}

		@Override
		protected ArrayList<RankElement> doInBackground(String... file) {

			image = FilesUtils.loadScaledBitmap(file[0]);

			File imageFile = new File(file[0]);
			if (imageFile.getParent().equals(getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath()))
				imageFile.delete();


			////			Log.e("fetType", Integer.toString(features.type()));
			//			int count1 = (int) (features.total() * features.channels());
			//			featuresBytes = new byte[count1];
			//			features.get(0, 0, featuresBytes);
			//
			////			Log.e("shType", Integer.toString(shapeDescriptors.type()));
			//			int count2 = (int) (shapeDescriptors.total() * shapeDescriptors.channels());
			//			shapeDescriptorsBytes = new int[count2];
			//			shapeDescriptors.get(0, 0, shapeDescriptorsBytes);

			try {
				conn = new DefaultTCPConnection(HOST, SERVER_PORT);
				return match(image, conn);
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}

		private ArrayList<RankElement> match(byte[] image, Connection conn) throws Exception {

			ManagingMessage request = new ManagingMessage(Type.MATCHING_REQUEST);

			request.putProperty("image", image);

			publishProgress(CONNECTION_STEP);
			conn.initialize();

			publishProgress(SEND_REQUEST_STEP);
			conn.send(request);

			publishProgress(MATCHING_STEP);
			ManagingMessage reply = conn.receive();
			if (reply.getType()==Type.RANKING)
				ranking = (ArrayList<RankElement>)reply.getProperty("ranking");
			conn.closeSocket();


			return ranking;
		}
	}


	/**
	 * KNOWN: the leaf has been correctly tagged and needs to be put in the tagged leaves list.
	 * UNKNOWN: the leaf has not been tagged, due to a connection error or a user interrupt. The user will be able to
	 * discard the tagging or store the picture to perform the tagging later on.
	 * PREVIOUSLY_UNKNOWN: the user is already requesting a tag for a previously stored leaf.
	 */
	public enum LeafState {

		KNOWN, UNKNOWN, PREVIOUSLY_UNKNOWN

	}


}
